{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Yield From - Two-Way Communications"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the last section on generators, we started looking at `yield from` and how we could delegate iteration to another iterator.\n",
    "\n",
    "Let's see a simple example again:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def squares(n):\n",
    "    for i in range(n):\n",
    "        yield i ** 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def delegator(n):\n",
    "    for value in squares(n):\n",
    "        yield value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "1\n",
      "4\n",
      "9\n",
      "16\n"
     ]
    }
   ],
   "source": [
    "gen = delegator(5)\n",
    "for _ in range(5):\n",
    "    print(next(gen))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alternatively we could write the same thing this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def delegator(n):\n",
    "    yield from squares(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "1\n",
      "4\n",
      "9\n",
      "16\n"
     ]
    }
   ],
   "source": [
    "gen = delegator(5)\n",
    "for _ in range(5):\n",
    "    print(next(gen))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Terminology:** \n",
    "When we use `yield from subgen` we are **delegating** to `subgen`.\n",
    "\n",
    "The generator that delegates to the other generator is called the **delegator** and the generator that it delegates to is called the **subgenerator**.\n",
    "\n",
    "So in our example `squares(n)` was the subgenerator, and `delegator()` was the delegator.\n",
    "\n",
    "The context that contains the code making `next` calls to the delegator, is called the **caller's context**, or simply the **caller**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What is actually happening when we call\n",
    "```\n",
    "next(gen)\n",
    "```\n",
    "is that `gen` (the delegator) is passing along the `next` request to the `squares(n)` (the subgenerator).\n",
    "\n",
    "In return, the subgenerator is yielding values back to the delegator, which in turn yields it back to us (the caller).\n",
    "\n",
    "There is in fact a **two-way communication channel** established between the caller and the subgenerator - all because of `yield from`.\n",
    "\n",
    "* caller: next --> delegator --> subgenerator\n",
    "* caller <-- delegator (yield) <-- subgenerator (yield)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, if `yield from` establishes this 2-way communication channel, and we can send `next` to the subgenerator via the delegator, can we send data using `send` as well?\n",
    "\n",
    "The answer is yes. We'll take a look at this in some detail over the next few videos.\n",
    "\n",
    "Let's start by looking at how the delegator works when a subgenerator closes by itself:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll want to inspect the delegator and the subgenerator, so let's import what we'll need from the `inspect` module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from inspect import getgeneratorstate, getgeneratorlocals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def song():\n",
    "    yield \"I'm a lumberjack and I'm OK\"\n",
    "    yield \"I sleep all night and I work all day\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def play_song():\n",
    "    count = 0\n",
    "    s = song()\n",
    "    yield from s\n",
    "    yield 'song finished'\n",
    "    print('player is exiting...')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here `play_song` is the delegator, and `song` is the subgenerator. We, the Jupyter notebook, are the caller."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "player = play_song()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GEN_CREATED\n",
      "{}\n"
     ]
    }
   ],
   "source": [
    "print(getgeneratorstate(player))\n",
    "print(getgeneratorlocals(player))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, no local variables have been created in `player` yet - that's because it is created, not actually started.\n",
    "\n",
    "Let's start it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"I'm a lumberjack and I'm OK\""
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(player)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's look at the state of things:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GEN_SUSPENDED\n",
      "{'s': <generator object song at 0x000002658B7E3F10>, 'count': 0}\n"
     ]
    }
   ],
   "source": [
    "print(getgeneratorstate(player))\n",
    "print(getgeneratorlocals(player))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now get a handle to the subgenerator `s`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "s = getgeneratorlocals(player)['s']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can check the state of `s`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GEN_SUSPENDED\n"
     ]
    }
   ],
   "source": [
    "print(getgeneratorstate(s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see the subgenerator is suspended.\n",
    "\n",
    "Let's iterate a few more times:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I sleep all night and I work all day\n",
      "GEN_SUSPENDED\n",
      "GEN_SUSPENDED\n"
     ]
    }
   ],
   "source": [
    "print(next(player))\n",
    "print(getgeneratorstate(player))\n",
    "print(getgeneratorstate(s))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "song finished\n",
      "GEN_SUSPENDED\n",
      "GEN_CLOSED\n"
     ]
    }
   ],
   "source": [
    "print(next(player))\n",
    "print(getgeneratorstate(player))\n",
    "print(getgeneratorstate(s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point the subgenerator exited, so its state is `GEN_CLOSED`, but the delegator (`player`) is just suspended, and in fact yielded `song finished`.\n",
    "\n",
    "We can advance one more time:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "player is exiting...\n"
     ]
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-17-6715b496a8f9>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mplayer\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "print(next(player))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We get the `StopIteration` exception because `player` returned, and now both the delegator and the subgenerator are in a closed state:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GEN_CLOSED\n",
      "GEN_CLOSED\n"
     ]
    }
   ],
   "source": [
    "print(getgeneratorstate(player))\n",
    "print(getgeneratorstate(s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Important to note here is that when the subgenerator returned, the delegator **continued running normally**.\n",
    "\n",
    "Let's make a tweak to our `player` generator to make this even more evident:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def player():\n",
    "    count = 1\n",
    "    while True:\n",
    "        print('Run count:', count)\n",
    "        yield from song()\n",
    "        count += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "p = player()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Run count: 1\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(\"I'm a lumberjack and I'm OK\", 'I sleep all night and I work all day')"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(p), next(p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Run count: 2\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(\"I'm a lumberjack and I'm OK\", 'I sleep all night and I work all day')"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(p), next(p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Run count: 3\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(\"I'm a lumberjack and I'm OK\", 'I sleep all night and I work all day')"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(p), next(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and so on..."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
